/*
 * pkgmgr.cpp
 *
 *  Created on: Jan 14, 2009
 *      Author: yospaly
 */

#include <wx/filename.h>
#include <wx/tarstrm.h>
#include <wx/wfstream.h>
#include <wx/dir.h>
#include <wx/zstream.h>

#include "pkgmgr.h"
#include "inibuilder.h"
#include "common.h"
#include "events.h"
#include "config.h"
#include "desktop.h"
#include "wininetstream.h"
#include "mounttable.h"
#include "wxbzip/bzipstream.h"
#include "scriptrunner.h"


wxThread::ExitCode PkgMaintainThread::Entry() {
	switch (PkgMgr::get()._task_type) {
	case PkgMgr::DOWNLOAD :
		return PkgMgr::get()._download_task();
	case PkgMgr::MAINTAIN :
		return PkgMgr::get()._maintain_task();
	}
}

wxThread::ExitCode PkgMgr::_remove_task() {
	// notify removal starting
    EventCenter::post(wxEVT_PKGS_REMOVE_BEGIN_EVENT, _remove_pkgs.GetCount());
	//wxThread::Sleep(100); // remove file
    
	for (int i=0; i<_remove_pkgs.GetCount() && false==_stop_tasks; i++) {
		EventCenter::post( wxEVT_PKG_REMOVE_BEGIN_EVENT, _remove_pkgs[i], i+1 );

	    _begin_run_script();
		EventCenter::post( wxEVT_RUN_PREREMOVE_SCRIPT_EVENT, _remove_pkgs[i] );
	    _wait_run_script();
	    
		wxArrayString files = IniBuilder::get().get_installed_files(_remove_pkgs[i]);
		
		if ( files.IsEmpty() ) {
			// error occurs
			EventCenter::post(wxEVT_PKG_REMOVE_COMPLETED_EVENT,
	    		_("No files need to be removed"), i );
			
		    continue;
		}

		for ( int j=0; j<files.GetCount() && false==_stop_tasks; j++ ) {
			EventCenter::post( wxEVT_PKG_REMOVING_EVENT, files[j], j+1 );
			
			// remove file
			wxString native = files[j];
			
			native = MountTable::get().cygpath(native);
			
			::wxRemoveFile(native);
			
			// TODO: remove links?
		}
		
		// remove *.lst.gz
		wxString lst_gz_path = MountTable::get().cygpath(CYGWIN_INSTALLED_LST_DIR);
		lst_gz_path.Append(_remove_pkgs[i]);
		lst_gz_path.Append(_T(".lst.gz"));
		
		if ( wxFile::Exists(lst_gz_path) ) {
			::wxRemoveFile(lst_gz_path);
		}
		
		EventCenter::post( wxEVT_PKG_REMOVE_COMPLETED_EVENT, i+1 );
		
		// mark package available
		// IniBuilder::get().set_pkg_status(_remove_pkgs[i], PkgInfo::AVAILABLE);
		IniBuilder::get().set_removed(_remove_pkgs[i]);
		
	}

	EventCenter::post( wxEVT_PKGS_REMOVE_COMPLETED_EVENT );
    return wxThread::ExitCode(true);
}

wxThread::ExitCode PkgMgr::_download_task() {
	const wxString& mirror = Config::get().get_last_mirror();
	
	_gen_download_list();
	
	EventCenter::post(wxEVT_PKGS_DOWNLOAD_BEGIN_EVENT, 
		        		_download_size
		        );
	
    size_t total_downloaded = 0;
	
	for ( int i=0; i<_download_pkgs.GetCount() && false==_stop_tasks; i++ ) {
		// wxMessageBox(_download_pkgs[i]);
		
	    size_t downloaded = 0;
		
		const wxString& install_path = IniBuilder::get().get_pkg_path(_download_pkgs[i]);
	    const wxString& file_path = _get_download_filepath(install_path);
	    const wxString& file_url = _get_download_fileurl(install_path);
		
		
	    // begin download, and notify the size of the file
	    unsigned long n = IniBuilder::get().get_pkg_size(_download_pkgs[i]);
	    EventCenter::post(wxEVT_PKG_DOWNLOAD_BEGIN_EVENT, _download_pkgs[i], n);
	    
	    if ( true==_is_cached(_download_pkgs[i], file_path) ) {
			total_downloaded = total_downloaded + n;
	        EventCenter::post(wxEVT_PKG_DOWNLOAD_COMPLETED_EVENT, total_downloaded);
	        
	    	continue;
	    }
	    
	    if (0==n) {
		    EventCenter::post(wxEVT_PKGS_DOWNLOAD_COMPLETED_EVENT,
		    		wxString::Format(_("Zero file size - url: %s"),install_path.c_str()),
		    		1
		    );

		    return wxThread::ExitCode(false);
		}
		
		// download "file_url" to "file_path"
		
		WinINetInputStream inet_stream(file_url);
		if ( !inet_stream.IsOk() ) {
		    EventCenter::post(wxEVT_PKGS_DOWNLOAD_COMPLETED_EVENT,
		    		wxString::Format(_("Can't open url: %s"),mirror.c_str()),
		    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
		    );
		    
		    return wxThread::ExitCode(false);
		}
		
		// TODO: check md5 for cached download file that already exists ?
		wxFFile ffile(file_path, _T("w+b"));
		
		const int BUF_SIZE = 1024*10; // 10K
		char buf[BUF_SIZE] = {0};
		
	    // size_t n2 = inet_stream.GetSize();
	    // wxMessageBox(wxString::Format(_T("%d"), n2));
	    
	    const wxString& connect_err = inet_stream.CheckConnection();
	    if ( !connect_err.IsEmpty() ) {
		    EventCenter::post(wxEVT_PKGS_DOWNLOAD_COMPLETED_EVENT,
		    		connect_err,
		    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
		    );

		    return wxThread::ExitCode(false);
	    }
	    
		while( !inet_stream.Eof() && false==_stop_tasks ) {
			if (!inet_stream.IsOk()) {
			    EventCenter::post(wxEVT_PKGS_DOWNLOAD_COMPLETED_EVENT,
			    		wxString::Format(_("Download error - url: %s"), install_path.c_str()),
			    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
			    );
			    
			    // wxMessageBox(_("Download error - url: %s"));
			    return wxThread::ExitCode(false);
			}
			
			// measure download speed
	        size_t len = inet_stream.Read(buf, BUF_SIZE).LastRead();
	        downloaded = downloaded + len;
	        
	        if (len < 1) {
	        	break;
	        }
	        
	        ffile.Write(buf, len);
	        
	        EventCenter::post(wxEVT_PKG_DOWNLOADING_EVENT, downloaded );
		}
	    
		total_downloaded = total_downloaded + n;
        EventCenter::post(wxEVT_PKG_DOWNLOAD_COMPLETED_EVENT, total_downloaded);
        
        ffile.Close();
	}
    
    EventCenter::post(wxEVT_PKGS_DOWNLOAD_COMPLETED_EVENT, 0);
    
    return wxThread::ExitCode(true);
}


bool PkgMgr::_is_cached(const wxString& pkg, const wxString& filepath) {
	if ( !wxFile::Exists(filepath) ) {
		return false;
	}
	
	const wxString& filesize = wxFileName::GetSize(filepath).ToString();
	const wxString& downloaded = IniBuilder::get().get_pkg_size_str(pkg);
	
	if (filesize==downloaded) {
		return true;
	}
	
	return false;
}


wxString PkgMgr::_get_download_filepath(const wxString& path /* relative path */ ) {
	wxString file_path = _download_dir;
	
	file_path.Append(_T("/"));
	file_path.Append(path);
	wxFileName(file_path).Mkdir(0700, wxPATH_MKDIR_FULL);
	
	return file_path;
}

wxString PkgMgr::_get_download_fileurl(const wxString& path /* relative path */ ) {
	const wxString& mirror = Config::get().get_last_mirror();
	wxString file_url = mirror;
	
	if (!file_url.EndsWith(_T("/"))) {
		file_url.Append(_T("/"));
	}
	
	file_url.Append(path);
	return file_url;
}



wxThread::ExitCode PkgMgr::_maintain_task() {
	wxThread::ExitCode ret;
	
	EventCenter::post( wxEVT_PKGS_MAINTIAN_BEGIN_EVENT );
	
	// remove package before upgrade
	for (int i=0; i<_upgrade_pkgs.GetCount(); i++) {
		_remove_pkgs.Add(_upgrade_pkgs[i]);
	}
	
	if ( !_remove_pkgs.IsEmpty() ) {
		ret = _remove_task();
	}
	
	if ( !_install_pkgs.IsEmpty() || !_reinstall_pkgs.IsEmpty() || !_upgrade_pkgs.IsEmpty() ) {
		ret = _install_task();
	}
	
	IniBuilder::get().regenerate_db();
	
	EventCenter::post( wxEVT_PKGS_MAINTIAN_COMPLETED_EVENT );
	
	return ret;
}

wxThread::ExitCode PkgMgr::_install_task() {
	// notify installing starting
    EventCenter::post(wxEVT_PKGS_INSTALL_BEGIN_EVENT, _download_pkgs.GetCount());
	//wxThread::Sleep(100); // remove file
    
	for (int i=0; i<_download_pkgs.GetCount() && false==_stop_tasks; i++) {
		EventCenter::post( wxEVT_PKG_INSTALL_BEGIN_EVENT, _download_pkgs[i], i+1 );
	    
		wxArrayString files = IniBuilder::get().get_installed_files(_download_pkgs[i]);
		
		if ( false==_extract_files(_download_pkgs[i]) ) {
			// error occurs
			EventCenter::post(wxEVT_PKG_INSTALL_COMPLETED_EVENT,
	    		_("Error when extract files"), i+1 );
			
		    continue;
		}

		// EventCenter::post( wxEVT_PKG_INSTALLING_EVENT, files[j], j+1 );
		
		// TODO: remove *.lst.gz
		
		EventCenter::post( wxEVT_PKG_INSTALL_COMPLETED_EVENT, i+1 );
		
	}
	
	wxArrayString scripts;
	wxDir::GetAllFiles(ScriptRunner::get().get_postinstall_dir(),
			&scripts, _T("*.sh"), wxDIR_FILES|wxDIR_HIDDEN);
	
	// Run postinstall scripts.
	for (int i=0; i<scripts.GetCount(); i++) {
		const wxString& name = wxFileName(scripts[i]).GetName();
		
	    _begin_run_script();
		EventCenter::post( wxEVT_RUN_POSTINSTALL_SCRIPT_EVENT, name );
	    _wait_run_script();
	}
	
	// postinstall actions, generate shortcuts bash script etc.
	Desktop::get().post_install();
    _begin_run_script();
    EventCenter::post( wxEVT_RUN_POSTINSTALL_SCRIPT_EVENT, _T("citrus-desktop") );
    _wait_run_script();
    
	EventCenter::post( wxEVT_PKGS_INSTALL_COMPLETED_EVENT );
	
    return wxThread::ExitCode(true);
}

// TODO: SYMBOL LINK
bool PkgMgr::_extract_files(const wxString& pkg) {
	const wxString& install_path = IniBuilder::get().get_pkg_path(pkg);
    const wxString& file_path = _get_download_filepath(install_path);
    
    // save files
	wxFFileInputStream temp_stream(file_path);
	if ( !temp_stream.IsOk() ) {
		return false;
	}
	
	wxBZipInputStream bz_stream(temp_stream);
	if ( !bz_stream.IsOk() ) {
		return false;
	}
	
	wxTarInputStream tar_stream(bz_stream);
	if ( !tar_stream.IsOk() ) {
		return false;
	}
	
	// save installed files path
	wxString lstfile = MountTable::get().cygpath(_T("/etc/setup"));
	wxFileName::Mkdir(lstfile, 0777, wxPATH_MKDIR_FULL);
	lstfile.Append(_T("/"));
	lstfile.Append(pkg);
	lstfile.Append(_T(".lst.gz"));
	
	wxFFileOutputStream list_stream(lstfile);
	wxZlibOutputStream gz_list_stream(list_stream, 9, wxZLIB_GZIP);
	
	wxTarEntry* entry = NULL;
	
	while ( NULL != (entry = tar_stream.GetNextEntry()) ) {
		wxString name = entry->GetName();
		name.Prepend(_T("/"));
		name.Replace(_T("\\"), _T("/"));

		wxString filepath = MountTable::get().cygpath(name);
		
		// write file posix name to package .lst file
		gz_list_stream.Write(name.mb_str(), name.Len());
		gz_list_stream.Write("\n", 1);
		
		// make dirs
		wxFileName wxfilename(filepath);
		if ( false==wxfilename.Mkdir(0777, wxPATH_MKDIR_FULL) ) {
			// error occurs
			goto clean_and_continue;
		}
		
		// extract file
		switch ( entry->GetTypeFlag() ) {
		case wxTAR_REGTYPE :
			{
				const int BUF_SIZE = 1024*10; // 10k
				char buf[BUF_SIZE] = {0};
				wxFFile file(filepath, _T("w+b"));
				
				while ( !tar_stream.Eof() ) {
					int len = tar_stream.Read(buf, BUF_SIZE).LastRead();
					file.Write(buf, len);
				}
	
				file.Close();
			}
			goto clean_and_continue;
		case wxTAR_LNKTYPE :
			{
				wxString link = entry->GetLinkName();
				if ( !link.StartsWith(_T("/")) ) {
					link.Prepend(_T("/"));
				}
				
				const wxString& link2 = MountTable::get().cygpath(link);
				wxCopyFile(link2, filepath);
			}
			goto clean_and_continue;
		case wxTAR_SYMTYPE :
			{
				const wxString& link = entry->GetLinkName();
				mkcygsymlink(filepath, link);
			}
			goto clean_and_continue;
		case wxTAR_DIRTYPE :
			wxFileName::Mkdir(filepath, 0777, wxPATH_MKDIR_FULL);
			goto clean_and_continue;
		default :
			break;
		}
		
		// EventCenter::post( wxEVT_PKG_INSTALLING_EVENT, name, 0 );
clean_and_continue:
		tar_stream.CloseEntry();
		delete entry;
		entry = NULL;
	}
	
	// mark package installed
	// IniBuilder::get().set_pkg_status(pkg, PkgInfo::UPDATED);
	IniBuilder::get().set_installed(pkg);
}


void PkgMgr::_gen_download_list() {
	_download_pkgs.Clear();
	_download_size = 0;
	
	for (int i=0; i<_install_pkgs.GetCount(); i++) {
		_download_pkgs.Add(_install_pkgs[i]);
	}
	for (int i=0; i<_upgrade_pkgs.GetCount(); i++) {
		_download_pkgs.Add(_upgrade_pkgs[i]);
	}
	for (int i=0; i<_reinstall_pkgs.GetCount(); i++) {
		_download_pkgs.Add(_reinstall_pkgs[i]);
	}
	
	
	for (int i=0; i<_download_pkgs.GetCount(); i++ ) {
		unsigned long size = IniBuilder::get().get_pkg_size(_download_pkgs[i]);
		_download_size = _download_size + size;
	}
}

PkgMgr::PkgMgr() {
	_download_dir = wxFileName::GetTempDir();
	if (!_download_dir.EndsWith(_T("/"))) {
		_download_dir.Append(_T("/"));
	}
	
    _download_dir.Append(_T("citrus_cache"));
	wxFileName::Mkdir(_download_dir, 0777, wxPATH_MKDIR_FULL);
	
	_download_size = 0;
}

PkgMgr::~PkgMgr() {
	// TODO Auto-generated destructor stub
}

PkgMgr PkgMgr::self;

